/**
 * Copyright (C) 2025 pony working team
 * SPDX-License-Identifier: MulanPSL-2.0
 */

/* includes (local) ----------------------------------------------------------*/
#include <hal_i2c.h>
#include <x_config.h>
/* includes (standard library, system) ---------------------------------------*/
/* includes (other library) --------------------------------------------------*/
/* includes (project) --------------------------------------------------------*/
#include <hal_gpio.h>
#include <hal_delay.h>

#include <x_module.h>
#include <x_warn.h>

#include <k_errno.h>

/* defines -------------------------------------------------------------------*/

// sync from stm32
#define I2C_OAR1_ADD0_Pos (0U)
#define I2C_OAR1_ADD0_Msk (0x1UL << I2C_OAR1_ADD0_Pos) /*!< 0x00000001 */
#define I2C_OAR1_ADD0     I2C_OAR1_ADD0_Msk            /*!<Bit 0 */

#define I2C_7BIT_ADD_WRITE(__ADDRESS__) ((uint8_t)((__ADDRESS__) & (uint8_t)(~I2C_OAR1_ADD0)))
#define I2C_7BIT_ADD_READ(__ADDRESS__)  ((uint8_t)((__ADDRESS__) | I2C_OAR1_ADD0))

#define I2C_10BIT_ADDRESS(__ADDRESS__) ((uint8_t)((uint16_t)((__ADDRESS__) & (uint16_t)0x00FF)))
#define I2C_10BIT_HEADER_WRITE(__ADDRESS__)                                                        \
    ((uint8_t)((uint16_t)((uint16_t)(((uint16_t)((__ADDRESS__) & (uint16_t)0x0300)) >> 7) |        \
                          (uint16_t)0x00F0)))
#define I2C_10BIT_HEADER_READ(__ADDRESS__)                                                         \
    ((uint8_t)((uint16_t)((uint16_t)(((uint16_t)((__ADDRESS__) & (uint16_t)0x0300)) >> 7) |        \
                          (uint16_t)(0x00F1))))

#define I2C_MEM_ADD_MSB(__ADDRESS__)                                                               \
    ((uint8_t)((uint16_t)(((uint16_t)((__ADDRESS__) & (uint16_t)0xFF00)) >> 8)))
#define I2C_MEM_ADD_LSB(__ADDRESS__) ((uint8_t)((uint16_t)((__ADDRESS__) & (uint16_t)0x00FF)))

/* typedefs ------------------------------------------------------------------*/

typedef struct sw_i2c {
    hal_i2c_t hal_i2c;
    hal_gpio_t gpio_scl;
    hal_gpio_t gpio_sda;
} sw_i2c_t;

/* macro ---------------------------------------------------------------------*/

#define I2CS_SCL_L() hal_gpio_write(&i2c->gpio_scl, 0)
#define I2CS_SCL_H() hal_gpio_write(&i2c->gpio_scl, 1)
#define I2CS_SDA_L() hal_gpio_write(&i2c->gpio_sda, 0)
#define I2CS_SDA_H() hal_gpio_write(&i2c->gpio_sda, 1)

#define I2CS_SDA_RD() hal_gpio_state(&i2c->gpio_sda)

#define I2CS_DELAY() hal_delay_us(5)

/* variables (extern) --------------------------------------------------------*/
/* variables (local) ---------------------------------------------------------*/

#ifdef CONFIG_SW_I2C1
static sw_i2c_t sw_i2c1;
#endif

/* variables (global) --------------------------------------------------------*/
/* inlines -------------------------------------------------------------------*/
/* functions -----------------------------------------------------------------*/

static void __sw_i2c_start(sw_i2c_t *i2c)
{
    I2CS_SDA_H();
    I2CS_SCL_H();
    I2CS_DELAY();
    I2CS_SDA_L();
    I2CS_DELAY();
    I2CS_SCL_L();
    I2CS_DELAY();
}

static void __sw_i2c_stop(sw_i2c_t *i2c)
{
    I2CS_SDA_L();
    I2CS_SCL_H();
    I2CS_DELAY();
    I2CS_SDA_H();
}

static void __sw_i2c_ack(sw_i2c_t *i2c)
{
    I2CS_SDA_L();
    I2CS_DELAY();
    I2CS_SCL_H();
    I2CS_DELAY();
    I2CS_SCL_L();
    I2CS_DELAY();
    I2CS_SDA_H();
}

static void __sw_i2c_nack(sw_i2c_t *i2c)
{
    I2CS_SDA_H();
    I2CS_DELAY();
    I2CS_SCL_H();
    I2CS_DELAY();
    I2CS_SCL_L();
    I2CS_DELAY();
}

static __unused uint8_t __sw_i2c_wait_ack(sw_i2c_t *i2c)
{
    uint8_t timeout = 0;

    I2CS_SDA_H();
    I2CS_DELAY();
    I2CS_SCL_H();
    I2CS_DELAY();

    while (I2CS_SDA_RD()) {
        timeout++;
        if (timeout > 250) {
            __sw_i2c_stop(i2c);
            return 1;   // no ack
        }
    }
    I2CS_SCL_L();
    I2CS_DELAY();
    return 0;   // ack
}

static void __sw_i2c_bsend(sw_i2c_t *i2c, uint8_t data)
{
    uint8_t i;

    for (i = 0; i < 8; i++) {
        if (data & 0x80) {
            I2CS_SDA_H();
        } else {
            I2CS_SDA_L();
        }
        I2CS_DELAY();
        I2CS_SCL_H();
        I2CS_DELAY();
        I2CS_SCL_L();
        if (i == 7) {
            I2CS_SDA_H();
        }
        data <<= 1;
        I2CS_DELAY();
    }
}

static uint8_t __sw_i2c_brecv(sw_i2c_t *i2c)
{
    uint8_t i;
    uint8_t data = 0;

    for (i = 0; i < 8; i++) {
        data <<= 1;
        I2CS_SCL_H();
        I2CS_DELAY();
        if (I2CS_SDA_RD()) {
            data |= 0x01;
        }
        I2CS_SCL_L();
        I2CS_DELAY();
    }
    I2CS_SCL_L();
    return data;
}

static __unused uint8_t __sw_i2c_bwrite(sw_i2c_t *i2c, uint8_t addr, uint16_t memaddr,
                                        uint8_t masize, uint8_t data)
{
    __sw_i2c_start(i2c);
    __sw_i2c_bsend(i2c, I2C_7BIT_ADD_WRITE(addr));
    if (__sw_i2c_wait_ack(i2c)) {
        return 1;
    }
    __sw_i2c_bsend(i2c, I2C_MEM_ADD_LSB(memaddr));
    if (__sw_i2c_wait_ack(i2c)) {
        return 1;
    }

    __sw_i2c_bsend(i2c, data);
    if (__sw_i2c_wait_ack(i2c)) {
        return 1;
    }

    __sw_i2c_stop(i2c);
    return 0;
}

// S    addr W As  memaddr   As  Data0   As    Data1   As  ... DataN   As  P
static uint8_t __sw_i2c_mem_write(sw_i2c_t *i2c, uint16_t addr, uint16_t memaddr, uint8_t masize,
                                  const uint8_t *data, uint16_t size)
{
    uint16_t i;

    __sw_i2c_stop(i2c);
    __sw_i2c_start(i2c);
    __sw_i2c_bsend(i2c, I2C_7BIT_ADD_WRITE(addr));
    if (__sw_i2c_wait_ack(i2c)) {
        return 1;
    }

    __sw_i2c_bsend(i2c, I2C_MEM_ADD_LSB(memaddr));
    if (__sw_i2c_wait_ack(i2c)) {
        return 1;
    }

    for (i = 0; i < size; i++) {
        __sw_i2c_bsend(i2c, data[i]);
        if (__sw_i2c_wait_ack(i2c)) {
            return 1;
        }
    }

    __sw_i2c_stop(i2c);
    return 0;
}

// S    addr W As  memaddr   As  Sr  addr   R   As  Data0   Am    Data1   Am  ... DataN   N  P
static uint8_t __sw_i2c_mem_read(sw_i2c_t *i2c, uint16_t addr, uint16_t memaddr, uint8_t masize,
                                 uint8_t *data, uint16_t size)
{
    uint16_t i;

    __sw_i2c_start(i2c);
    __sw_i2c_bsend(i2c, I2C_7BIT_ADD_WRITE(addr));
    if (__sw_i2c_wait_ack(i2c)) {
        return 1;
    }
    __sw_i2c_bsend(i2c, I2C_MEM_ADD_LSB(memaddr));
    if (__sw_i2c_wait_ack(i2c)) {
        return 1;
    }

    __sw_i2c_start(i2c);
    __sw_i2c_bsend(i2c, I2C_7BIT_ADD_READ(addr));
    if (__sw_i2c_wait_ack(i2c)) {
        return 1;
    }

    for (i = 0; i < size; i++) {
        data[i] = __sw_i2c_brecv(i2c);
        if (i != size - 1) {
            __sw_i2c_ack(i2c);
        } else {
            __sw_i2c_nack(i2c);
        }
    }

    __sw_i2c_stop(i2c);
    return 0;
}

static int __sw_i2c_xfer(hal_i2c_t *hal_i2c, uint16_t addr, const uint8_t *data, uint16_t size)
{
    return -ENOSYS;
}

static int __sw_i2c_recv(hal_i2c_t *hal_i2c, uint16_t addr, uint8_t *data, uint16_t size)
{
    return -ENOSYS;
}

static int __sw_i2c_write(hal_i2c_t *hal_i2c, uint16_t addr, uint16_t memaddr, uint8_t masize,
                          const uint8_t *data, uint16_t size)
{
    uint8_t status;

    sw_i2c_t *i2c = container_of(hal_i2c, sw_i2c_t, hal_i2c);

    status = __sw_i2c_mem_write(i2c, addr, memaddr, 1, data, size);

    return status == 0 ? (int)size : 0;
}

static int __sw_i2c_read(hal_i2c_t *hal_i2c, uint16_t addr, uint16_t memaddr, uint8_t masize,
                         uint8_t *data, uint16_t size)
{
    uint8_t status;

    sw_i2c_t *i2c = container_of(hal_i2c, sw_i2c_t, hal_i2c);

    status = __sw_i2c_mem_read(i2c, addr, memaddr, 1, data, size);

    return status == 0 ? (int)size : 0;
}

static const hal_i2c_methods_t sw_i2c_methods = {
    .xfer = __sw_i2c_xfer,
    .recv = __sw_i2c_recv,
    .write = __sw_i2c_write,
    .read = __sw_i2c_read,
};

static int __sw_i2c_init(sw_i2c_t *i2c, uint8_t sclgpiox, uint8_t sclpin, uint8_t sdagpiox,
                         uint8_t sdapin, const char *name)
{
    i2c->gpio_scl.gpio = sclgpiox;
    i2c->gpio_scl.pin = sclpin;
    i2c->gpio_scl.mode = HAL_GPIO_MODE_OUTPUT_OD;

    if (hal_gpio_init(&i2c->gpio_scl) != 0) {
        WARN("hal_gpio_init scl failed");
        return -1;
    }

    i2c->gpio_sda.gpio = sdagpiox;
    i2c->gpio_sda.pin = sdapin;
    i2c->gpio_sda.mode = HAL_GPIO_MODE_OUTPUT_OD;

    if (hal_gpio_init(&i2c->gpio_sda) != 0) {
        WARN("hal_gpio_init sda failed");
        return -1;
    }

    return hal_i2c_register(&i2c->hal_i2c, name, &sw_i2c_methods, 0);
}

static int sw_i2c_init(void)
{
#ifdef CONFIG_SW_I2C1
    if (__sw_i2c_init(&sw_i2c1, CONFIG_SW_I2C1_SCL_PORT, CONFIG_SW_I2C1_SCL_PIN,
                      CONFIG_SW_I2C1_SDA_PORT, CONFIG_SW_I2C1_SDA_PIN, "sw_i2c1") != 0) {
        return -1;
    }
#endif

    return 0;
}

MODULE_INIT(bus, sw_i2c_init);
